"""
补丁检查服务
"""
import os
import sys
import zipfile
import hashlib
import tempfile
from pathlib import Path
from typing import Dict, List, Optional, Tuple

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from services.progress_service import progress_service
from utils_class_match.file_utils import normalize_path
from file_utils import normalize_jar_path, jar_path_to_system_path


class PatchCheckService:
    """补丁检查服务类"""
    
    def __init__(self, upload_folder: str):
        """
        初始化补丁检查服务
        
        Args:
            upload_folder: 上传文件目录
        """
        self.upload_folder = upload_folder
        self._temp_dirs = []  # 用于存储临时目录，延迟清理
    
    def check_patch(self, jar_path: str, zip_path: str, excel_data: List[Dict],
                   progress_key: Optional[str] = None) -> Dict:
        """
        检查补丁文件
        
        Args:
            jar_path: JAR包路径
            zip_path: 压缩包路径
            excel_data: Excel数据列表，每个元素包含：lib包、路径、文件名
            progress_key: 进度键（任务ID）
        
        Returns:
            Dict: 检查结果，包含：
                - results: 检查结果列表
                - summary: 统计信息
        """
        # 初始化进度
        total_checks = len(excel_data)
        if progress_key:
            progress_service.init_progress(progress_key, total=total_checks)
            progress_service.update_progress(progress_key, status='initializing')
        
        # 收集JAR包中的所有文件信息
        jar_files = self._collect_jar_files(jar_path, progress_key)
        
        if progress_key:
            progress_service.update_progress(progress_key, status='extracting_zip')
        
        # 提取压缩包文件
        zip_files = self._extract_zip_files(zip_path, progress_key)
        
        if progress_key:
            progress_service.update_progress(progress_key, status='checking')
            # 更新总数为检查项数量（因为前面收集文件时可能已经更新了total）
            with progress_service._lock:
                if progress_key in progress_service._progress_data:
                    progress_service._progress_data[progress_key]['total'] = total_checks
        
        # 执行检查
        results = []
        
        for idx, excel_item in enumerate(excel_data):
            if progress_key:
                progress_service.update_progress(progress_key, 
                                               processed=idx + 1)
            
            lib_package = excel_item.get('lib包', '').strip()
            path = excel_item.get('路径', '').strip()
            filename = excel_item.get('filename', '').strip()
            
            if not filename:
                results.append({
                    'lib包': lib_package,
                    '路径': path,
                    '文件名': filename,
                    '状态': '错误',
                    '消息': '文件名为空',
                    'jar中存在': False,
                    'zip中存在': False,
                    '内容一致': False
                })
                continue
            
            # 构建JAR包中的目标路径
            jar_target_path = self._build_jar_path(lib_package, path, filename)
            
            # 构建压缩包中的目标路径（相对路径）
            zip_target_path = os.path.join(path, filename).replace('\\', '/') if path else filename
            
            # 检查JAR包中的文件
            jar_file_info = self._find_file_in_jar(jar_files, jar_target_path, lib_package)
            
            # 检查压缩包中的文件
            zip_file_info = self._find_file_in_zip(zip_files, zip_target_path)
            
            # 调试：如果找不到文件，打印一些信息
            if not zip_file_info and zip_files:
                # 打印前几个zip文件路径作为参考
                sample_paths = list(zip_files.keys())[:5]
                print(f"调试: 找不到文件 '{zip_target_path}'，压缩包中的示例路径: {sample_paths}")
            
            # 对比文件
            check_result = self._compare_files(jar_file_info, zip_file_info, 
                                            lib_package, path, filename)
            results.append(check_result)
        
        # 生成统计信息
        summary = {
            'total': len(results),
            'jar_exists': sum(1 for r in results if r['jar中存在']),
            'zip_exists': sum(1 for r in results if r['zip中存在']),
            'both_exist': sum(1 for r in results if r['jar中存在'] and r['zip中存在']),
            'content_match': sum(1 for r in results if r.get('内容一致', False)),
            'jar_missing': sum(1 for r in results if not r['jar中存在']),
            'zip_missing': sum(1 for r in results if not r['zip中存在']),
            'content_mismatch': sum(1 for r in results if r.get('jar中存在') and r.get('zip中存在') and not r.get('内容一致', False))
        }
        
        # 标记任务完成
        if progress_key:
            progress_service.update_progress(progress_key, 
                                           processed=total_checks,
                                           status='completed')
        
        # 在返回结果之前清理临时文件
        self._cleanup_temp_files()
        
        return {
            'results': results,
            'summary': summary
        }
    
    def _collect_jar_files(self, jar_path: str, progress_key: Optional[str]) -> Dict[str, Dict]:
        """
        收集JAR包中的所有文件信息（包括嵌套JAR包）
        
        Args:
            jar_path: JAR包路径
            progress_key: 进度键
        
        Returns:
            Dict: 文件信息字典，key为路径，value为文件信息
        """
        files = {}
        temp_dir = tempfile.mkdtemp()
        # 将临时目录添加到列表中，延迟清理
        self._temp_dirs.append(temp_dir)
        
        try:
            with zipfile.ZipFile(jar_path, 'r') as jar:
                file_list = jar.namelist()
                
                if progress_key:
                    progress_service.update_progress(progress_key, status='collecting_jar')
                
                for entry_info in jar.infolist():
                    entry_path = normalize_path(entry_info.filename)
                    
                    # 跳过目录
                    if entry_path.endswith('/'):
                        continue
                    
                    # 检查是否是嵌套的JAR包
                    if entry_path.endswith('.jar') and 'lib' in entry_path.lower():
                        nested_files = self._process_nested_jar(
                            jar, entry_info, entry_path, temp_dir, progress_key
                        )
                        files.update(nested_files)
                    else:
                        # 处理普通文件
                        file_hash = self._calculate_file_hash(jar.read(entry_info))
                        files[entry_path] = {
                            'path': entry_path,
                            'hash': file_hash,
                            'size': entry_info.file_size,
                            'is_nested': False
                        }
        
        except Exception as e:
            print(f"Error collecting files from jar {jar_path}: {e}")
            if progress_key:
                progress_service.update_progress(progress_key, 
                                               status=f'error: {str(e)}')
        
        # 不再在这里清理临时目录，延迟到check_patch方法返回前清理
        return files
    
    def _process_nested_jar(self, parent_jar: zipfile.ZipFile, 
                           entry_info: zipfile.ZipInfo, entry_path: str,
                           temp_dir: str, progress_key: Optional[str]) -> Dict[str, Dict]:
        """
        处理嵌套JAR包
        
        Args:
            parent_jar: 父JAR包
            entry_info: 嵌套JAR的ZipInfo
            entry_path: 嵌套JAR的路径
            temp_dir: 临时目录
            progress_key: 进度键
        
        Returns:
            Dict: 嵌套JAR包中的文件信息
        """
        files = {}
        
        try:
            # 提取嵌套JAR到临时文件
            with tempfile.NamedTemporaryFile(delete=False, suffix='.jar', dir=temp_dir) as temp_jar:
                temp_jar.write(parent_jar.read(entry_info))
                temp_jar_path = temp_jar.name
            
            # 读取嵌套JAR
            with zipfile.ZipFile(temp_jar_path, 'r') as nested_jar:
                for nested_info in nested_jar.infolist():
                    nested_path = normalize_path(nested_info.filename)
                    
                    if nested_path.endswith('/'):
                        continue
                    
                    # 构建完整路径：嵌套JAR路径 + 文件路径
                    full_path = f"{entry_path}/{nested_path}"
                    
                    file_hash = self._calculate_file_hash(nested_jar.read(nested_info))
                    files[full_path] = {
                        'path': full_path,
                        'hash': file_hash,
                        'size': nested_info.file_size,
                        'is_nested': True,
                        'nested_jar_path': entry_path
                    }
            
            # 删除临时文件
            os.unlink(temp_jar_path)
        
        except Exception as e:
            print(f"Error processing nested jar {entry_path}: {e}")
        
        return files
    
    def _extract_zip_files(self, zip_path: str, progress_key: Optional[str]) -> Dict[str, Dict]:
        """
        提取压缩包中的所有文件信息
        
        Args:
            zip_path: 压缩包路径
            progress_key: 进度键
        
        Returns:
            Dict: 文件信息字典，key为相对路径，value为文件信息
        """
        files = {}
        
        try:
            # 直接读取压缩包内的文件，保持原始路径格式
            with zipfile.ZipFile(zip_path, 'r') as zip_file:
                file_list = zip_file.namelist()
                
                if progress_key:
                    progress_service.update_progress(progress_key, status='extracting_zip')
                
                # 遍历压缩包内的所有文件
                for file_name in file_list:
                    # 跳过目录条目
                    if file_name.endswith('/') or file_name.endswith('\\'):
                        continue
                    
                    # 规范化路径（统一使用 '/' 作为分隔符）
                    normalized_path = file_name.replace('\\', '/')
                    
                    try:
                        # 读取文件内容并计算哈希
                        with zip_file.open(file_name) as f:
                            file_content = f.read()
                            file_hash = self._calculate_file_hash(file_content)
                        
                        file_size = len(file_content)
                        files[normalized_path] = {
                            'path': normalized_path,
                            'hash': file_hash,
                            'size': file_size,
                            'original_path': file_name  # 保留原始路径格式
                        }
                    except Exception as e:
                        # 如果某个文件读取失败，跳过它
                        print(f"Warning: 无法读取压缩包中的文件 {file_name}: {e}")
                        continue
        
        except Exception as e:
            print(f"Error reading zip {zip_path}: {e}")
            if progress_key:
                progress_service.update_progress(progress_key, 
                                               status=f'error: {str(e)}')
        
        return files
    
    def _build_jar_path(self, lib_package: str, path: str, filename: str) -> str:
        """
        构建JAR包中的目标路径
        
        Args:
            lib_package: lib包名称
            path: 路径
            filename: 文件名
        
        Returns:
            str: JAR包中的完整路径
        """
        if lib_package:
            # lib包中的文件
            parts = ["BOOT-INF", "lib", lib_package]
            if path:
                parts.append(path)
            parts.append(filename)
        else:
            # 主JAR包中的文件
            parts = ["BOOT-INF", "classes"]
            if path:
                parts.append(path)
            parts.append(filename)
        
        return '/'.join(parts)
    
    def _find_file_in_jar(self, jar_files: Dict[str, Dict], 
                          target_path: str, lib_package: str) -> Optional[Dict]:
        """
        在JAR包中查找文件
        
        Args:
            jar_files: JAR包文件字典
            target_path: 目标路径
            lib_package: lib包名称
        
        Returns:
            Optional[Dict]: 文件信息，如果不存在则返回None
        """
        # 直接匹配
        if target_path in jar_files:
            return jar_files[target_path]
        
        # 尝试规范化路径匹配（不区分大小写）
        normalized_target = normalize_path(target_path).lower()
        for jar_path, file_info in jar_files.items():
            normalized_jar_path = normalize_path(jar_path).lower()
            if normalized_jar_path == normalized_target:
                return file_info
        
        # 如果是在lib包中，尝试匹配嵌套JAR的路径
        if lib_package:
            lib_package_normalized = lib_package.strip()
            # 构建可能的lib包名（带和不带.jar后缀）
            lib_package_variants = [lib_package_normalized]
            if not lib_package_normalized.endswith('.jar'):
                lib_package_variants.append(lib_package_normalized + '.jar')
            else:
                # 如果带.jar，也尝试不带.jar的版本
                lib_package_variants.append(lib_package_normalized[:-4])
            
            # 从target_path中提取路径部分（去掉BOOT-INF/lib/{lib_package}前缀）
            target_parts = normalize_path(target_path).split('/')
            if len(target_parts) >= 4 and target_parts[0].lower() == 'boot-inf' and target_parts[1].lower() == 'lib':
                lib_index = 2
                if lib_index < len(target_parts):
                    actual_lib_name = target_parts[lib_index]
                    # 检查lib包名是否匹配（不区分大小写）
                    if actual_lib_name.lower() in [v.lower() for v in lib_package_variants]:
                        # 提取内部路径（lib包名之后的所有部分）
                        inner_path = '/'.join(target_parts[lib_index + 1:])
                        # 尝试所有可能的嵌套JAR路径格式
                        for lib_var in lib_package_variants:
                            nested_jar_path = f"BOOT-INF/lib/{lib_var}"
                            nested_full_path = f"{nested_jar_path}/{inner_path}"
                            # 直接匹配
                            if nested_full_path in jar_files:
                                return jar_files[nested_full_path]
                            # 规范化匹配
                            normalized_nested = normalize_path(nested_full_path).lower()
                            for jar_path, file_info in jar_files.items():
                                if normalize_path(jar_path).lower() == normalized_nested:
                                    return file_info
            
            # 如果上面的逻辑没找到，尝试在所有嵌套JAR文件中查找
            # 提取目标文件名和路径
            target_filename = os.path.basename(target_path)
            # 从target_path中提取目录部分
            target_dir_parts = []
            if len(target_parts) >= 4 and target_parts[0].lower() == 'boot-inf' and target_parts[1].lower() == 'lib':
                lib_idx = 2
                if lib_idx < len(target_parts) - 1:  # 至少要有文件名
                    target_dir_parts = target_parts[lib_idx + 1:-1]  # 排除lib包名和文件名
            
            for jar_path, file_info in jar_files.items():
                # 检查是否是嵌套JAR中的文件
                if file_info.get('is_nested', False):
                    nested_jar_path = file_info.get('nested_jar_path', '')
                    if nested_jar_path:
                        # 检查lib包名是否匹配（不区分大小写）
                        nested_jar_name = os.path.basename(nested_jar_path).lower()
                        if nested_jar_name in [v.lower() for v in lib_package_variants]:
                            # 检查文件名是否匹配
                            jar_filename = os.path.basename(jar_path)
                            if jar_filename.lower() == target_filename.lower():
                                # 检查路径是否匹配
                                jar_path_parts = normalize_path(jar_path).split('/')
                                # 找到嵌套JAR路径后的部分
                                jar_inner_parts = []
                                found_jar = False
                                for part in jar_path_parts:
                                    if found_jar:
                                        jar_inner_parts.append(part)
                                    elif part.lower().endswith('.jar'):
                                        found_jar = True
                                
                                # 比较路径部分（不包含文件名）
                                if len(jar_inner_parts) > 0:
                                    jar_dir_parts = jar_inner_parts[:-1]
                                    if jar_dir_parts == target_dir_parts:
                                        return file_info
                                elif len(target_dir_parts) == 0:
                                    # 如果目标路径没有目录部分，也匹配
                                    return file_info
        
        # 最后尝试：只匹配文件名（作为最后的备选方案）
        target_filename = os.path.basename(target_path)
        if lib_package:
            lib_package_normalized = lib_package.strip()
            lib_package_variants = [lib_package_normalized]
            if not lib_package_normalized.endswith('.jar'):
                lib_package_variants.append(lib_package_normalized + '.jar')
            else:
                lib_package_variants.append(lib_package_normalized[:-4])
            
            # 在所有嵌套JAR文件中查找匹配的文件名
            for jar_path, file_info in jar_files.items():
                if file_info.get('is_nested', False):
                    nested_jar_path = file_info.get('nested_jar_path', '')
                    if nested_jar_path:
                        nested_jar_name = os.path.basename(nested_jar_path).lower()
                        if nested_jar_name in [v.lower() for v in lib_package_variants]:
                            jar_filename = os.path.basename(jar_path)
                            if jar_filename.lower() == target_filename.lower():
                                return file_info
        
        return None
    
    def _find_file_in_zip(self, zip_files: Dict[str, Dict], 
                          target_path: str) -> Optional[Dict]:
        """
        在压缩包中查找文件
        
        Args:
            zip_files: 压缩包文件字典
            target_path: 目标路径（相对路径）
        
        Returns:
            Optional[Dict]: 文件信息，如果不存在则返回None
        """
        # 直接匹配
        if target_path in zip_files:
            return zip_files[target_path]
        
        # 尝试规范化路径匹配（不区分大小写）
        normalized_target = normalize_path(target_path).lower()
        for zip_path, file_info in zip_files.items():
            normalized_zip_path = normalize_path(zip_path).lower()
            if normalized_zip_path == normalized_target:
                return file_info
        
        # 尝试匹配路径的结尾部分（处理压缩包可能有额外前缀的情况）
        # 例如：target_path = "com/example/file.class"
        # zip_path 可能是 "patch/com/example/file.class" 或 "com/example/file.class"
        target_parts = normalized_target.split('/')
        for zip_path, file_info in zip_files.items():
            normalized_zip_path = normalize_path(zip_path).lower()
            zip_parts = normalized_zip_path.split('/')
            # 如果zip路径以target路径结尾
            if len(zip_parts) >= len(target_parts):
                if zip_parts[-len(target_parts):] == target_parts:
                    return file_info
        
        # 尝试只匹配文件名和父目录（处理路径顺序可能不同的情况）
        target_filename = os.path.basename(target_path).lower()
        target_dir = os.path.dirname(target_path).lower() if '/' in target_path else ''
        if target_dir:
            target_dir_parts = normalize_path(target_dir).split('/')
            for zip_path, file_info in zip_files.items():
                normalized_zip_path = normalize_path(zip_path).lower()
                zip_filename = os.path.basename(normalized_zip_path).lower()
                if zip_filename == target_filename:
                    zip_dir = os.path.dirname(normalized_zip_path).lower() if '/' in normalized_zip_path else ''
                    if zip_dir:
                        zip_dir_parts = zip_dir.split('/')
                        # 检查目录部分是否匹配（允许有额外的前缀）
                        if len(zip_dir_parts) >= len(target_dir_parts):
                            if zip_dir_parts[-len(target_dir_parts):] == target_dir_parts:
                                return file_info
                    elif not target_dir:
                        # 如果都没有目录，只匹配文件名
                        return file_info
        
        # 最后尝试：只匹配文件名（不区分大小写）
        for zip_path, file_info in zip_files.items():
            if os.path.basename(zip_path).lower() == target_filename:
                return file_info
        
        return None
    
    def _compare_files(self, jar_file_info: Optional[Dict], 
                      zip_file_info: Optional[Dict],
                      lib_package: str, path: str, filename: str) -> Dict:
        """
        对比文件
        
        Args:
            jar_file_info: JAR包中的文件信息
            zip_file_info: 压缩包中的文件信息
            lib_package: lib包名称
            path: 路径
            filename: 文件名
        
        Returns:
            Dict: 检查结果
        """
        jar_exists = jar_file_info is not None
        zip_exists = zip_file_info is not None
        
        if not jar_exists and not zip_exists:
            return {
                'lib包': lib_package,
                '路径': path,
                '文件名': filename,
                '状态': '缺失',
                '消息': 'JAR包和压缩包中都不存在',
                'jar中存在': False,
                'zip中存在': False,
                '内容一致': False
            }
        
        if jar_exists and not zip_exists:
            return {
                'lib包': lib_package,
                '路径': path,
                '文件名': filename,
                '状态': '部分缺失',
                '消息': '压缩包中不存在',
                'jar中存在': True,
                'zip中存在': False,
                '内容一致': False,
                'jar_hash': jar_file_info.get('hash', ''),
                'jar_size': jar_file_info.get('size', 0)
            }
        
        if not jar_exists and zip_exists:
            return {
                'lib包': lib_package,
                '路径': path,
                '文件名': filename,
                '状态': '部分缺失',
                '消息': 'JAR包中不存在',
                'jar中存在': False,
                'zip中存在': True,
                '内容一致': False,
                'zip_hash': zip_file_info.get('hash', ''),
                'zip_size': zip_file_info.get('size', 0)
            }
        
        # 两者都存在，比较内容
        jar_hash = jar_file_info.get('hash', '')
        zip_hash = zip_file_info.get('hash', '')
        content_match = (jar_hash == zip_hash)
        
        if content_match:
            return {
                'lib包': lib_package,
                '路径': path,
                '文件名': filename,
                '状态': '一致',
                '消息': '文件存在且内容一致',
                'jar中存在': True,
                'zip中存在': True,
                '内容一致': True,
                'jar_hash': jar_hash,
                'zip_hash': zip_hash,
                'jar_size': jar_file_info.get('size', 0),
                'zip_size': zip_file_info.get('size', 0)
            }
        else:
            return {
                'lib包': lib_package,
                '路径': path,
                '文件名': filename,
                '状态': '不一致',
                '消息': '文件存在但内容不一致',
                'jar中存在': True,
                'zip中存在': True,
                '内容一致': False,
                'jar_hash': jar_hash,
                'zip_hash': zip_hash,
                'jar_size': jar_file_info.get('size', 0),
                'zip_size': zip_file_info.get('size', 0)
            }
    
    def _calculate_file_hash(self, file_content: bytes) -> str:
        """
        计算文件内容的MD5哈希值
        
        Args:
            file_content: 文件内容
        
        Returns:
            str: MD5哈希值
        """
        return hashlib.md5(file_content).hexdigest()
    
    def _cleanup_temp_files(self):
        """
        清理所有临时文件和目录
        在检查结果返回后调用
        """
        import shutil
        for temp_dir in self._temp_dirs:
            try:
                shutil.rmtree(temp_dir, ignore_errors=True)
            except Exception as e:
                print(f"Warning: 清理临时目录失败 {temp_dir}: {e}")
        # 清空列表
        self._temp_dirs.clear()

